/**
 * Copyright (C) Olafur Gauti Gudmundsson
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.solrom;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import org.solrom.annotations.IndexDocument;
import org.solrom.annotations.IndexField;
import org.solrom.annotations.IndexFieldGetter;
import org.solrom.annotations.IndexFieldSetter;
import org.solrom.annotations.IndexSerializedField;

/**
 * This class is used by Solrom to validate that the classes being mapped
 * are correctly annotated.
 * 
 * @author Olafur Gauti Gudmundsson
 */
class Validator {

	/**
	 * Takes a class, validates its annotations, and returns a MappedClass
	 * instance that contains a set of all the field names needed in a query
	 * to build an instance of the object from a SolrDocument.
	 * 
	 * @param c the class to validate
	 * @return a MappedClass with all the query field names
	 */
	static MappedClass validate( Class c ) {
		int idCount = 0;
		MappedClass mappedClass = new MappedClass(c);
		
		// if class name is mapped to a field, then that must be stored
		try {
			IndexDocument indexDoc = Utils.getIndexDocumentAnnotation(c);
			if ( indexDoc != null && !indexDoc.classNameField().equals("none") ) {
				mappedClass.addQueryFieldName(indexDoc.classNameField());
			}
		} catch ( Exception e ) {
			throw new RuntimeException(e);
		}
		
		// validate annotated fields
		for ( Field field : Utils.getDeclaredAndInheritedFields(c) ) {
			field.setAccessible(true);
			if ( field.isAnnotationPresent(IndexField.class) ) {
				if ( !Utils.isSimpleType(field.getType()) ) {
					throw new IndexFieldMappingException("Field [" + field.getName() + "] in class [" + c.getName() + "] is annotated as @IndexField, but is of invalid type: " + field.getType().getName());
				}
				IndexField indexField = field.getAnnotation(IndexField.class);
				if ( indexField.id() ) {
					idCount++;
				}
				String name = field.getName();
				if ( !indexField.name().equals("fieldName") ) {
					name = indexField.name();
				}
				
				// annotated fields are both read and written to when mapped
				mappedClass.addQueryFieldName(name);
                
            } else if ( field.isAnnotationPresent(IndexSerializedField.class) ) {
                IndexSerializedField indexField = field.getAnnotation(IndexSerializedField.class);
                
				String name = field.getName();
				if ( !indexField.name().equals("fieldName") ) {
					name = indexField.name();
				}
				
				// annotated fields are both read and written to when mapped
				mappedClass.addQueryFieldName(name);
			}
		}
		
		// validate annotated methods
		for ( Method method : c.getMethods() ) {
			if ( method.isAnnotationPresent(IndexFieldGetter.class) ) {
				if ( !Utils.hasValidReturnType(method) ) {
					throw new IndexFieldMappingException("Method [" + method.getName() + "] in class [" + c.getName() + "] is annotated as @IndexFieldGetter, but has an invalid return type: " + method.getReturnType());
				}
				IndexFieldGetter ifg = method.getAnnotation(IndexFieldGetter.class);
				if ( ifg.id() ) {
					idCount++;
				}
				
			} else if ( method.isAnnotationPresent(IndexFieldSetter.class) ) {
				if ( !Utils.hasValidArgument(method) ) {
					throw new IndexFieldMappingException("Method [" + method.getName() + "] in class [" + c.getName() + "] is annotated as @IndexFieldSetter, but has an invalid argument list.");
				}
				IndexFieldSetter indexField = method.getAnnotation(IndexFieldSetter.class);
				String name;
				if ( !indexField.name().equals("fieldName") ) {
					name = indexField.name();
				} else {
					name = Utils.getSetterPropertyName(method);
				}
				mappedClass.addQueryFieldName(name);
			}
		}
		
		if ( idCount == 0 ) {
			throw new IndexFieldMappingException("No method or field annotated as ID in class [" + c.getName() + "]");
		}
		if ( idCount > 1 ) {
			throw new IndexFieldMappingException("Multiple methods/fields annotated as ID in class [" + c.getName() + "]");
		}
		
		return mappedClass;
	}
}
